﻿using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace YK
{

    public class VideoCapture : ISampleGrabberCB, IVideo, IDisposable
    {
        [DllImport("Kernel32.dll", EntryPoint = "RtlMoveMemory")]
        private static extern void CopyMemory(IntPtr destination, IntPtr source, [MarshalAs(UnmanagedType.U4)] int length);

        private readonly Guid Capture = new Guid(0xfb6c4281, 0x0353, 0x11d1, 0x90, 0x5f, 0x00, 0x00, 0xc0, 0xcc, 0x16, 0xba);
        private readonly Guid Video = new Guid(0x73646976, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
        private readonly Guid VideoInfo = new Guid(0x05589f80, 0xc356, 0x11ce, 0xbf, 0x01, 0x00, 0xaa, 0x00, 0x55, 0x59, 0x5a);
        private readonly Guid RGB24 = new Guid(0xe436eb7d, 0x524f, 0x11ce, 0x9f, 0x53, 0x00, 0x20, 0xaf, 0x0b, 0xa7, 0x70);
        private readonly Guid VideoInputDevice = new Guid(0x860BB310, 0x5D01, 0x11d0, 0xBD, 0x3B, 0x00, 0xA0, 0xC9, 0x11, 0xCE, 0x86);
        private readonly IMediaControl _MediaControl = (IMediaControl)new FilterGraphNoThread();
        private readonly ISampleGrabber _SampleGrabber = (ISampleGrabber)new SampleGrabber();

        private WriteableBitmap _WriteableBitmap;

        private int _FrameWidth, _FrameHeight, _FrameBufferSize;
        private IntPtr _PSampleBuffer;
        private Rectangle _RectangleSource, _RectangleDestination;
        private RotateFlipType _RotateFlipType;


        public event Action<ImageSource> ImageSourceChanged;

        public void Init(int cameraIndex, int frameWidth = 640, int frameHeight = 480, ECameraAngle angle = ECameraAngle.A0)
        {
            _FrameWidth = frameWidth;
            _FrameHeight = frameHeight;
            _FrameBufferSize = frameWidth * frameHeight * 3;
            _PSampleBuffer = Marshal.AllocCoTaskMem(_FrameBufferSize);
            _RectangleSource = new Rectangle(0, 0, frameWidth, frameHeight);
            switch (angle)
            {
                case ECameraAngle.A0:
                    _RectangleDestination = _RectangleSource;
                    _RotateFlipType = RotateFlipType.RotateNoneFlipXY;
                    break;
                case ECameraAngle.A90:
                    _RotateFlipType = RotateFlipType.Rotate90FlipNone;
                    _RectangleDestination = new Rectangle(0, 0, _FrameHeight, _FrameWidth);
                    break;
                case ECameraAngle.A270:
                    _RotateFlipType = RotateFlipType.Rotate270FlipNone;
                    _RectangleDestination = new Rectangle(0, 0, _FrameHeight, _FrameWidth);
                    break;
                case ECameraAngle.A180:
                    _RectangleDestination = _RectangleSource;
                    _RotateFlipType = RotateFlipType.RotateNoneFlipNone;
                    break;
            }


            var graphBuilder = (IGraphBuilder)_MediaControl;
            var captureGraphBuilder2 = (ICaptureGraphBuilder2)new CaptureGraphBuilder2();
            int hr = captureGraphBuilder2.SetFiltergraph(graphBuilder);
            ThrowExceptionForHR(hr);

            //此方法可以将 source filter 上的 output pin 连接到 sink filter，也可以通过中间 filter 连接。
            hr = captureGraphBuilder2.RenderStream(Capture,//[in]指向 AMPROPERTY_PIN_CATEGORY 属性集中的 pin 类别的指针（请参见 Pin属性集）。PIN_CATEGORY_CAPTURE、PIN_CATEGORY_PREVIEW、PIN_CATEGORY_CC
                                         Video,//[in]指向主要类型 GUID 的指针，该 GUID 指定输出 pin 的媒体类型；或 NULL 以使用任何 pin，而与媒体类型无关。有关可能值的列表，请参考 Media Types and Sub Types。
                                         SetupCamera(cameraIndex, captureGraphBuilder2, (IGraphBuilder2)graphBuilder),//[in]指定一个指向连接的起始 filter 或输出 pin 的指针。
                                         null,//[in]指向中间 filter （例如压缩 filter）的 IBaseFilter 接口的指针。可以为 NULL。
                                         SetupSampleGrabber(graphBuilder));//[in]指向 sink filter（例如 renderer 或 mux filter）的 IBaseFilter 接口的指针。如果值为 NULL，则该方法使用默认的 renderer（渲染器）。
            ThrowExceptionForHR(hr);
            Marshal.ReleaseComObject(captureGraphBuilder2);
        }
        public void Play()
        {
            if (!Application.Current.Dispatcher.CheckAccess())
            {
                Application.Current.Dispatcher.Invoke(() => Play());
                return;
            }

            _WriteableBitmap = new WriteableBitmap(_RectangleDestination.Width, _RectangleDestination.Height, 96, 96, PixelFormats.Bgr24, null);
            ImageSourceChanged?.Invoke(_WriteableBitmap);
            _MediaControl.Run();
        }
        public void Stop()
        {
            if (!Application.Current.Dispatcher.CheckAccess())
            {
                Application.Current.Dispatcher.Invoke(() => Stop());
                return;
            }
            ImageSourceChanged?.Invoke(null);

            _MediaControl.Stop();
        }
        public Bitmap GetCurrentFrame()
        {
            _SampleGrabber.GetCurrentBuffer(ref _FrameBufferSize, _PSampleBuffer);
            var bitmap = new Bitmap(_FrameWidth, _FrameHeight);
            var bmpData = bitmap.LockBits(_RectangleSource, ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
            CopyMemory(bmpData.Scan0, _PSampleBuffer, _FrameBufferSize);
            bitmap.UnlockBits(bmpData);
            bitmap.RotateFlip(_RotateFlipType);
            return bitmap;

        }
        /// <summary>
        /// SampleGrabber回调的第0个方法，本类不使用
        /// </summary>
        /// <param name="SampleTime"></param>
        /// <param name="pSample">不应该是IntPtr类型，因为不使用所以乱写了个</param>
        /// <returns></returns>
        public int SampleCB(double SampleTime, IntPtr pSample) => 0;
        /// <summary>
        /// SampleGrabber回调的第1个方法
        /// </summary>
        /// <param name="SampleTime"></param>
        /// <param name="pBuffer">图片地址</param>
        /// <param name="BufferLen">图片长度</param>
        /// <returns></returns>
        public int BufferCB(double SampleTime, IntPtr pBuffer, int BufferLen)
        {

            _WriteableBitmap.Dispatcher.BeginInvoke((Action)delegate
            {
                if (_RotateFlipType == RotateFlipType.RotateNoneFlipNone)
                {
                    _WriteableBitmap.Lock();
                    CopyMemory(_WriteableBitmap.BackBuffer, pBuffer, BufferLen);
                    _WriteableBitmap.AddDirtyRect(new Int32Rect(0, 0, _RectangleDestination.Width, _RectangleDestination.Height));
                    _WriteableBitmap.Unlock();

                }
                else
                {
                    using var bitmap = new Bitmap(_FrameWidth, _FrameHeight);
                    var bmpData = bitmap.LockBits(_RectangleSource, ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
                    CopyMemory(bmpData.Scan0, pBuffer, BufferLen);
                    bitmap.UnlockBits(bmpData);
                    bitmap.RotateFlip(_RotateFlipType);
                    bmpData = bitmap.LockBits(_RectangleDestination, ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format24bppRgb);

                    _WriteableBitmap.Lock();
                    CopyMemory(_WriteableBitmap.BackBuffer, bmpData.Scan0, BufferLen);
                    _WriteableBitmap.AddDirtyRect(new Int32Rect(0, 0, _RectangleDestination.Width, _RectangleDestination.Height));
                    _WriteableBitmap.Unlock();
                    bitmap.UnlockBits(bmpData);

                }
            });
            return 0;
        }

        public void Dispose()
        {
            Stop();
            Marshal.ReleaseComObject(_MediaControl);
            Marshal.ReleaseComObject(_SampleGrabber);
            if (_PSampleBuffer != IntPtr.Zero)
                Marshal.FreeCoTaskMem(_PSampleBuffer);
        }
        /// <summary>
        /// 设置图片格式为rgb24，并允许GetCurrentBuffer
        /// </summary>
        /// <param name="_SampleGrabber"></param>
        private IBaseFilter SetupSampleGrabber(IGraphBuilder graphBuilder)
        {
            var mediaType = new AMMediaType
            {
                majorType = Video,
                subType = RGB24,
                formatType = VideoInfo
            };

            int hr = _SampleGrabber.SetMediaType(mediaType);
            ThrowExceptionForHR(hr);

            hr = _SampleGrabber.SetBufferSamples(true);
            ThrowExceptionForHR(hr);
            hr = _SampleGrabber.SetCallback(this, 1);
            ThrowExceptionForHR(hr);

            var filterGrabber = this._SampleGrabber as IBaseFilter;
            hr = graphBuilder.AddFilter(filterGrabber, "SampleGrabber");
            ThrowExceptionForHR(hr);

            return filterGrabber;
        }


        private IBaseFilter SetupCamera(int cameraIndex, ICaptureGraphBuilder2 captureGraphBuilder2, IGraphBuilder2 graphBuilder2)
        {
            ICreateDevEnum enumDev = (ICreateDevEnum)new CreateDevEnum();
            int hr = enumDev.CreateClassEnumerator(VideoInputDevice, out IEnumMoniker enumMon, 0);
            ThrowExceptionForHR(hr);

            IMoniker[] mon = new IMoniker[1];
            if (cameraIndex > 0)
            {
                hr = enumMon.Skip(cameraIndex);
                ThrowExceptionForHR(hr);
            }
            hr = enumMon.Next(1, mon, IntPtr.Zero);
            ThrowExceptionForHR(hr);

            hr = graphBuilder2.AddSourceFilterForMoniker(mon[0], null, "Camera", out IBaseFilter filterCamera);
            ThrowExceptionForHR(hr);

            if (filterCamera is null)
                throw new Exception($"无法连接摄像头{cameraIndex}");


            hr = captureGraphBuilder2.FindInterface(Capture, Video, filterCamera, typeof(IAMStreamConfig).GUID, out var streamConfig);
            ThrowExceptionForHR(hr);

            var videoStreamConfig = streamConfig as IAMStreamConfig;

            hr = videoStreamConfig.GetFormat(out var media);
            ThrowExceptionForHR(hr);

            var videoInfo = new VideoInfoHeader();
            Marshal.PtrToStructure(media.formatPtr, videoInfo);
            videoInfo.BmiHeader.Width = _FrameWidth;
            videoInfo.BmiHeader.Height = _FrameHeight;
            Marshal.StructureToPtr(videoInfo, media.formatPtr, false);

            hr = videoStreamConfig.SetFormat(media);
            Marshal.FreeHGlobal(media.formatPtr);
            if (hr < 0)
                throw new Exception($"摄像头不支持{_FrameWidth}*{_FrameHeight}");

            return filterCamera;

        }
        private void ThrowExceptionForHR(int hr, [CallerMemberName] string memberName = "", [CallerLineNumber] int lineNumber = 0)
        {
            if (hr < 0)
                throw new Exception($"{memberName}/{lineNumber}错误：" + hr);
        }
    }
}

